package service

import (
	"crypto/tls"
	"ddnser/DTO"
	"ddnser/common/logger"
	"ddnser/settings"
	"ddnser/utils/errcode"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"regexp"
	"strings"

	"github.com/go-mail/mail"
	"github.com/spf13/viper"

	"go.uber.org/zap"
)

type NotificationService interface {
	Notify([]net.IP, []DTO.UpdateRes) errcode.ErrCode
}

type AllNotification struct {
	logger *zap.SugaredLogger
}

func NewAllNotification() *AllNotification {
	return &AllNotification{logger: logger.GetLogger()}
}

func (allN *AllNotification) Notify(ips []net.IP, res []DTO.UpdateRes) (code errcode.ErrCode) {
	conf := settings.GetAppConf().NotificationConf
	if !conf.Enable {
		allN.logger.Infof("notification disabled")
		return errcode.OK
	}
	if conf.MailConf.Enable {
		mailN := NewMailNotiService()
		mailN.Notify(ips, res)
	}
	if conf.ServerchanConf.Enable {
		serverchanN := NewServerchanService()
		serverchanN.Notify(ips, res)
	}
	code = errcode.OK
	return code
}

type MailNotiService struct {
	logger   *zap.SugaredLogger
	smtp     string
	smtpport int
	user     string
	password string
	mailto   string
	title    string
}

func NewMailNotiService() *MailNotiService {
	c := settings.GetAppConf().MailConf
	return &MailNotiService{
		logger:   logger.GetLogger(),
		smtp:     c.Smtp,
		smtpport: c.SmtpPort,
		user:     c.User,
		password: c.Password,
		mailto:   c.MailTo,
		title:    c.Title,
	}
}

func (mailN *MailNotiService) Notify(ips []net.IP, res []DTO.UpdateRes) (code errcode.ErrCode) {
	d := mail.NewDialer(mailN.smtp, mailN.smtpport, mailN.user, mailN.password)
	d.TLSConfig = &tls.Config{ServerName: mailN.smtp}
	d.StartTLSPolicy = mail.MandatoryStartTLS
	rawMailTo := strings.Split(
		strings.Trim(viper.GetString("notification.mail.mailto"), ","),
		",",
	)
	re := regexp.MustCompile(`[^@\.]+@[^@\.]+\.[^@\.]+`)
	var mailTo []string
	for _, to := range rawMailTo {
		to = strings.TrimSpace(to)
		if !re.MatchString(to) {
			continue
		}
		mailTo = append(mailTo, to)
	}
	content := mailN.formatUpdateResAsHtml(ips, res)
	msg := mailN.CreateMsg(mailN.title, content, mailTo)
	if err := d.DialAndSend(msg); err != nil {
		code = errcode.NOTIFY_MAIL_SEND_ERR
		mailN.logger.Errorf("mail.Notify: [%d]-%s", code, err)
		return
	}
	code = errcode.OK
	return
}

func (mailN *MailNotiService) CreateMsg(subject string, content string, mailto []string) (msg *mail.Message) {
	msg = mail.NewMessage(mail.SetEncoding(mail.Base64), mail.SetCharset("UTF-8"))
	msg.SetHeaders(map[string][]string{
		"From":    {msg.FormatAddress(mailN.user, "ddnser updator")},
		"To":      mailto,
		"Subject": {subject},
	})
	msg.SetBody("text/html", content)
	return
}

func (mailN *MailNotiService) formatUpdateResAsHtml(ips []net.IP, res []DTO.UpdateRes) string {
	var msg []string
	msg = append(msg, func() string {
		var tmp []string
		for _, ip := range ips {
			tmp = append(tmp, ip.String())
		}
		return fmt.Sprintf("<p>Basic Infomation</p><p>update with ips:<b>%s</b></p>", strings.Join(tmp, ","))
	}())
	for idx, r := range res {
		msg = append(msg, fmt.Sprintf("<p>%d-%s</p><p><b>%s</b>: %s</p>", idx+1, r.DdnsType, r.Host, r.Msg))
	}
	return strings.Join(msg, "<hr>")
}

type ServerchanService struct {
	logger  *zap.SugaredLogger
	sendkey string
	title   string
	channel string
}

func NewServerchanService() *ServerchanService {
	conf := settings.GetAppConf().NotificationConf.ServerchanConf
	return &ServerchanService{
		logger:  logger.GetLogger(),
		sendkey: conf.SendKey,
		title:   conf.Title,
		channel: conf.Channel,
	}
}

func (serverchan *ServerchanService) Notify(ips []net.IP, res []DTO.UpdateRes) (code errcode.ErrCode) {
	sendbody := map[string]interface{}{
		"title":   serverchan.title,
		"channel": serverchan.channel,
		"desp":    serverchan.formatUpdateResAsMD(ips, res),
	}
	b, err := json.Marshal(sendbody)
	if err != nil {
		code = errcode.NOTIFY_SERVERCHAN_JSON_ERR
		serverchan.logger.Errorf("serverchan.Notify: [%d]-json.Marshal err:%s", code, err)
		return code
	}
	uri := fmt.Sprintf("https://sctapi.ftqq.com/%s.send", serverchan.sendkey)
	client := http.Client{}
	resp, err := client.Post(
		uri,
		"application/json",
		strings.NewReader(string(b)),
	)
	if err != nil {
		code = errcode.NOTIFY_SERVERCHAN_POST_ERR
		serverchan.logger.Errorf("serverchan.Notify: [%d]-client.Post err:%s", code, err)
		return code
	}
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		code = errcode.OK
	} else {
		code = errcode.FAIL
	}
	serverchan.logger.Infof("serverchan.Notify: [%d]-%s", code, errcode.GetCodeMsg(code))
	body, _ := io.ReadAll(resp.Body)
	serverchan.logger.Infof("serverchan.Notify: [%d]-reply:%s", code, string(body))
	return
}

func (serverchan *ServerchanService) formatUpdateResAsMD(ips []net.IP, res []DTO.UpdateRes) string {
	var msg []string
	msg = append(msg, func() string {
		var tmp []string
		for _, ip := range ips {
			tmp = append(tmp, ip.String())
		}
		return fmt.Sprintf("## Basic Infomation\r\n\r\nupdate with ips:**%s**", strings.Join(tmp, ","))
	}())
	for idx, r := range res {
		msg = append(msg, fmt.Sprintf("## %d-%s\r\n\r\n**%s**: %s", idx+1, r.DdnsType, r.Host, r.Msg))
	}
	return strings.Join(msg, "\r\n\r\n")
}
